﻿using System;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Cubicle.Core.DataContracts;
using Cubicle.Core.Shared;
using Cubicle.Excel.AddIn;
using Cubicle.Excel.Hosting;
using Cubicle.Excel.Shared;
using ExcelDna.Integration;

namespace Cubicle.Excel.Functions
{
    public static class EngineFunctions
    {

        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        #region Functions: Invocation

        /// <summary>
        /// A call to the Invocation API
        /// </summary>
        /// <param name="target">The target of the invocation (either a handle or class name)</param>
        /// <param name="memberName">The member to invoke (use .ctor to call constructor or .ctorc to call custom constructor)</param>
        /// <param name="parameters">(Optional) The parameters to pass to the target</param>
        /// <param name="key">(Optional) The key to use when storing results. Omitting this parameters will return the result instead of storing.</param>
        /// <param name="methodArgs">(Optional) The type names for generic method arguments.</param>
        /// <param name="classArgs">(Optional) The type names for generic class arguments</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>The result of the invoke action.</returns>
        [ExcelFunction(
            Name = "cInvoke",
            Category = "Cubicle (Invocation)",
            Description = "Manipulate the Cubicle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Invoke(
            string target,
            string memberName,
            object[,] parameters,
            object key,
            object[] methodArgs = null,
            object[] classArgs = null,
            string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var result = engine.Invoke(target, memberName, parameters, key, methodArgs, classArgs);
            return result;
        }

        /// <summary>
        /// A multi-threaded invoke - use only with threadsafe methods.
        /// </summary>
        /// <param name="target">The target of the invocation (either a handle or class name)</param>
        /// <param name="memberName">The member to invoke (use .ctor to call constructor or .ctorc to call custom constructor)</param>
        /// <param name="parameters">(Optional) The parameters to pass to the target</param>
        /// <param name="key">(Optional) The key to use when storing results. Omitting this parameters will return the result instead of storing.</param>
        /// <param name="methodArgs">(Optional) The type names for generic method arguments.</param>
        /// <param name="classArgs">(Optional) The type names for generic class arguments</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>The result of the invoke action.</returns>
        [ExcelFunction(
            Name = "cInvokeMt",
            Category = "Cubicle (Invocation)",
            Description = "Manipulate the Cubicle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static object InvokeMt(
            string target,
            string memberName,
            object[,] parameters,
            object key,
            object[] methodArgs = null,
            object[] classArgs = null,
            string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var result = engine.Invoke(target, memberName, parameters, key, methodArgs, classArgs);
            return result;
        }

        [ExcelFunction(
            Name = "cInvokeRepeat",
            Category = "Cubicle (Invocation)",
            Description = "Periodically issue invoke statements",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object InvokeRepeat(
            int milliseconds,
            string target,
            string memberName,
            object[,] parameters)
        {
            var engine = CubicleAddIn.Engines.DefaultClient;
            var obs = Observable
                .Interval(TimeSpan.FromMilliseconds(milliseconds))
                .Select(_ => engine.Invoke(target, memberName, parameters));
            var pars = new object[] { target, memberName, parameters };
            return RxExcel.Observe("cInvokeRepeat", pars, () => obs);
        }

        [ExcelFunction(
            Name = "cMake",
            Category = "Cubicle (Invocation)",
            Description = "Periodically issue invoke statements",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Make(
            string target,
            string memberName,
            object[,] parameters,
            object key)
        {
            var engine = CubicleAddIn.Engines.DefaultClient;
            if (key != null && !(key is ExcelMissing)) return engine.Invoke(target, memberName, parameters, key);
            var pars = new object[] { target, memberName, parameters };
            return ExcelObjectHandle.CreateHandle("cMake", pars, () => engine.Invoke(target, memberName, parameters, key), (h) => engine.Remove(h.ToString()));
        }

        [ExcelFunction(
            Name = "cInvokeBuffer",
            Category = "Cubicle (Invocation)",
            Description = "Periodically issue invoke statements and return a rolling buffer.",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object InvokeBuffer(
            int bufferSize,
            int milliseconds,
            string target,
            string memberName,
            object[,] parameters,
            object missing)
        {
            bufferSize = Math.Max(1, bufferSize);
            if (missing is ExcelMissing) missing = @"N/A";
            var engine = CubicleAddIn.Engines.DefaultClient;
            var startingValues = Enumerable
                .Repeat(missing, bufferSize)
                .ToObservable();
            var invoked = Observable
                .Interval(TimeSpan.FromMilliseconds(milliseconds))
                .Select(_ => engine.Invoke(target, memberName, parameters));
            var obs = startingValues.Concat(invoked)
                .Buffer(bufferSize, 1)
                .Select(x => ExcelHelpers.JaggedToDouble(x.Reverse().ToArray()));
            var pars = new object[] { target, memberName, parameters };
            return RxExcel.Observe("cInvokeBuffer", pars, () => obs);
        }

        #endregion

        #region Functions: Creation

        /// <summary>
        /// A convenience function that simplifies constructor calls to the Invocation API
        /// </summary>
        /// <param name="target">The target of the invocation (either a handle or class name)</param>
        /// <param name="parameters">(Optional) The parameters to pass to the target</param>
        /// <param name="key">(Optional) The key to use when storing results. Omitting this parameters will return the result instead of storing.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns></returns>
        [ExcelFunction(
            Name = "cCreate",
            Category = "Cubicle (Creation)",
            Description = "Create an object using the object's constructor",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Create(
            string target,
            object[,] parameters,
            object key,
            string engineAlias = null)
        {
            return Invoke(target, ".ctor", parameters, key, null, null, engineAlias);
        }

        /// <summary>
        /// A convenience function that simplifies custom constructor calls to the Invocation API
        /// </summary>
        /// <param name="target">The target of the invocation (either a handle or class name)</param>
        /// <param name="parameters">(Optional) The parameters to pass to the target</param>
        /// <param name="key">(Optional) The key to use when storing results. Omitting this parameters will return the result instead of storing.</param>
        /// <param name="classArgs">(Optional) The type names for generic class arguments</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns></returns>
        [ExcelFunction(
            Name = "cCustomCreate",
            Category = "Cubicle (Creation)",
            Description = "Create an object using custom logic",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object CustomCreate(
            string target,
            object[,] parameters,
            object key,
            object[] classArgs,
            string engineAlias = null)
        {
            return Invoke(target, ".ctorc", parameters, key, null, classArgs, engineAlias);
        }

        #endregion

        #region Functions: Repository management

        /// <summary>
        /// List all handles in the repository
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>All handles in the repository</returns>
        [ExcelFunction(
            Name = "cList",
            Category = "Cubicle (Repository)",
            Description = "List repository contents",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] List(string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            return engine.List().ToRange();
        }

        /// <summary>
        /// Retrieve an object from the repository
        /// </summary>
        /// <param name="handle">An object handle</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>The requested object</returns>
        [ExcelFunction(
            Name = "cGet",
            Category = "Cubicle (Repository)",
            Description = "Get data associated with a handle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Get(string handle, string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var result = engine.Get(handle);
            return result;
        }

        /// <summary>
        /// Store spreadsheet values in the repository
        /// </summary>
        /// <param name="data">The range of data to store</param>
        /// <param name="key">The key to use for handle generation</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>An object handle</returns>
        [ExcelFunction(
            Name = "cSet",
            Category = "Cubicle (Repository)",
            Description = "Add data to the repository and generate a handle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string Set(object data, object key = null, string engineAlias = null)
        {
            var handleStr = key == ExcelMissing.Value || key == null ? null : key.ToString();
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            return engine.Set(handleStr, data);
        }

        /// <summary>
        /// Store spreadsheet values in the repository
        /// </summary>
        /// <param name="data">The range of data to store</param>
        /// <param name="key">The key to use for handle generation</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>An object handle</returns>
        [ExcelFunction(
            Name = "cPush",
            Category = "Cubicle (Repository)",
            Description = "Add data to the repository and generate a handle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Push(object data, object key = null, string engineAlias = null)
        {
            var handleStr = key == ExcelMissing.Value || key == null ? null : key.ToString();
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var pars = new [] { data, key, engineAlias };
            return ExcelObjectHandle.CreateHandle("cPush", pars, () => engine.Set(handleStr, data), h => engine.Remove(h.ToString()));
        }

        /// <summary>
        /// Remove an object from the repository
        /// </summary>
        /// <param name="handle">The handle to remove</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cRemove",
            Category = "Cubicle (Repository)",
            Description = "Remove a handle from the repository",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool Remove(string handle, string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var robj = engine.Remove(handle);
            return true;
        }

        /// <summary>
        /// Clear all objects from the repository
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cClear",
            Category = "Cubicle (Repository)",
            Description = "Clear the repository contents",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool Clear(string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            engine.Clear();
            return true;
        }

        #endregion

        #region Functions: Engine management

        /// <summary>
        /// Create a Local Cubicle
        /// </summary>
        /// <param name="engineAlias">An alias for this Cubicle.</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cCreateEngine",
            Category = "Cubicle (Management)",
            Description = "Create an empty Engine",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool CreateEngine(string engineAlias)
        {
            return CubicleAddIn.Engines.Create(engineAlias);
        }

        /// <summary>
        /// Return the version of the Cubicle Host
        /// </summary>
        /// <param name="engineAlias">An alias for this Cubicle.</param>
        /// <returns>The version of the Cubicle Host</returns>
        [ExcelFunction(
            Name = "cHostVersion",
            Category = "Cubicle (Misc)",
            Description = "Get the version of the Cubicle Host",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string HostVersion(string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            return engine.GetVersion();
        }

        /// <summary>
        /// Create an App-Domain Cubicle
        /// </summary>
        /// <param name="engineAlias">An alias for this Cubicle.</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cCreateEngineAppDomain",
            Category = "Cubicle (Management)",
            Description = "Create an empty Engine in a new AppDomain",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool CreateEngineAppDomain(string engineAlias)
        {
            return CubicleAddIn.Engines.CreateAppDomain(engineAlias);
        }

        /// <summary>
        /// Establish a connection to a remote Cubicle host.
        /// </summary>
        /// <param name="address">The address of the Cubicle API web-service</param>
        /// <param name="engineAlias">An alias for this Cubicle.</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cConnectEngine",
            Category = "Cubicle (Management)",
            Description = "Connect to a remote Engine",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool ConnectEngine(string address, string engineAlias)
        {
            return CubicleAddIn.Engines.Connect(engineAlias, address);
        }

        /// <summary>
        /// Returns a list of all connections in the Engine Manager
        /// </summary>
        /// <param name="withheadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <returns>Connection information</returns>
        [ExcelFunction(
            Name = "cListEngines",
            Category = "Cubicle (Management)",
            Description = "List enginees",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListEngines(bool withheadings = false)
        {
            return CubicleAddIn.Engines.Clients.Values.ToRange(withheadings, x => new
            {
                Alias = x.Alias,
                Type = x.GetType().FullName,
                IsConnected = x.IsConnected(),
                IsDefault = x == CubicleAddIn.Engines.DefaultClient,
                IsHosted = x.IsHosted,
                Address = x is LocalProxy && x.IsHosted
                    ? x.HostAddress
                    : x.Address
            });
        }

        /// <summary>
        /// Instruct the Engine Manager to disconnect an Cubicle proxy.
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cDisconnectEngine",
            Category = "Cubicle (Management)",
            Description = "Disconnect an Cubicle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool DisconnectEngine(string engineAlias)
        {
            return CubicleAddIn.Engines.Remove(engineAlias);
        }

        /// <summary>
        /// Set the alias of the default Cubicle proxy.
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cSetDefaultEngine",
            Category = "Cubicle (Management)",
            Description = "Specify the name of the default Engine",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool SetDefaultEngine(string engineAlias)
        {
            return CubicleAddIn.Engines.MakeDefault(engineAlias);
        }

        /// <summary>
        /// Return the alias of the default Cubicle proxy.
        /// </summary>
        /// <returns>The alias of the default Cubicle proxy</returns>
        [ExcelFunction(
            Name = "cGetDefaultEngine",
            Category = "Cubicle (Management)",
            Description = "Retrieve the default Engine",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string GetDefaultEngine()
        {
            return CubicleAddIn.Engines.DefaultClient.Alias;
        }

        /// <summary>
        /// Preforms a call to the Hosting API of an Cubicle to start self-hosting
        /// </summary>
        /// <param name="engineAlias">An alias for this Cubicle</param>
        /// <param name="hostingType">Must be one of 'http', 'pipe' or 'tcp'</param>
        /// <param name="port">(Optional) This is only not used for 'pipe' </param>
        /// <param name="enableMetaData">Set to true to enable clients to reflect web-service information via a MEX endpoint</param>
        /// <returns>The address of the newly created Cubicle API web-service endpoint</returns>
        [ExcelFunction(
            Name = "cStartHost",
            Category = "Cubicle (Management)",
            Description = "Host a Engine over the designated protocol.",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string StartEngineHost(
            string engineAlias,
            [ExcelArgument(Description = "Must be one of 'http', 'pipe' or 'tcp'")] 
            string hostingType,
            int port, bool enableMetaData = true)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            if (engine == null) return "Engine not found.";
            if (port == 0) port = 20101;
            switch (hostingType.ToLower())
            {
                case "http":
                    return engine.StartHost(HostingType.Http, enableMetaData, port);
                case "pipe":
                    return engine.StartHost(HostingType.Pipe, enableMetaData, port);
                case "tcp":
                    return engine.StartHost(HostingType.Tcp, enableMetaData, port);
                default:
                    return "Hosting type must be one of 'http', 'pipe' or 'tcp' ";
            }
        }

        /// <summary>
        /// Preforms a call to the Hosting API of an Cubicle to stop self-hosting
        /// </summary>
        /// <param name="engineAlias">An alias for this Cubicle</param>
        [ExcelFunction(
            Name = "cStopHost",
            Category = "Cubicle (Management)",
            Description = "Host a Engine over the designated protocol.",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool StopEngineHost(string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            return engine != null && engine.StopHost();
        }

        #endregion

        #region Functions: Pipeline processors

        /// <summary>
        /// Reconstructs the processor pipeline used by the Invocation Engine
        /// </summary>
        /// <param name="processorNames">The list of processors to use</param>
        /// <param name="alias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Success/Failure</returns>
        //[ExcelFunction(
        //   Name = "cBuildProcessPipeline",
        //   Category = "Cubicle (Pipeline)",
        //   Description = "List the pipeline processors of the Cubicle",
        //   IsExceptionSafe = false,
        //   IsHidden = false,
        //   IsMacroType = false,
        //   IsThreadSafe = false,
        //   IsVolatile = false)]
        //public static bool BuildProcessPipeline(object[] processorNames, string alias = null)
        //{
        //    //var Engine = CubicleAddIn.Engines.Get(alias);
        //    //return Engine.BuildPipeline(processorNames.Cast<string>());
        //    return false;
        //}

        #endregion

        #region Functions: Assembly resolution

        /// <summary>
        /// Loads an assembly from a file
        /// </summary>
        /// <param name="path">The path to the assembly</param>
        /// <param name="autoReg">(Optional) set to true to automatically scan the assembly for function bindings</param>
        /// <param name="explicitExports">(Optional) set to true to only bind functions marked with ExcelDNA attributes</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>The FullName of the loaded assembly</returns>
        [ExcelFunction(
            Name = "cLoadFromFile",
            Category = "Cubicle (Misc)",
            Description = "Try to find an assembly from a file-name and load it",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string LoadFromFile(string path, bool autoReg, bool explicitExports, string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var isLocal = engine is LocalProxy;
            if (autoReg && !isLocal)
            {
                var message = "Cannot auto register function against a non-local Engine";
                _logger.Error(message);
                return message;
            }
            path = ExcelHelpers.MakeRooted(path);
            if (!File.Exists(path)) return null;
            var rawAsm = Helpers.ReadBytesFromFile(path);
            var fullName = engine.LoadAssembly(rawAsm);

            // For non-local engines or if auto reg is not required, just return the full name
            if (!autoReg || !isLocal) return fullName;

            // Retrieve the loaded assembly
            var asm = AppDomain.CurrentDomain
                .GetAssemblies()
                .Where(a => a.FullName == fullName)
                .FirstOrDefault();

            // Return the full name
            return fullName;
        }

        /// <summary>
        /// Binds the methods of a loaded assembly
        /// </summary>
        /// <param name="fullName">The fullName of the assembly to bind</param>
        /// <param name="explicitExports">(Optional) set to true to only bind functions marked with ExcelDNA attributes</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "cRegisterFunctions",
            Category = "Cubicle (Misc)",
            Description = "Scan an assembly and register all matching signatures as worksheet functions",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool RegisterFunctions(string fullName, bool explicitExports)
        {
            var asm = TypeCache.GetAssemblyFromName(fullName);
            if (asm == null) return false;
            CustomHelpers.AutoRegisterDnaMethods(asm, explicitExports);
            return true;
        }


        #endregion

        #region Functions: Scripting

        //private static string _script;
        //private static string _scriptTarget;

        //[ExcelFunction(
        //    Name = "cSetScript",
        //    Category = "Cubicle",
        //    Description = "Set path to an .cube script",
        //    IsExceptionSafe = false,
        //    IsHidden = false,
        //    IsMacroType = false,
        //    IsThreadSafe = true,
        //    IsVolatile = false)]
        //public static bool SetScript(string scriptFile, string name)
        //{
        //    if (!File.Exists(scriptFile)) return false;
        //    _script = scriptFile;
        //    _scriptTarget = name.GetType() == typeof(ExcelMissing) 
        //        ? null 
        //        : name;
        //    return true;
        //}

        /// <summary>
        /// Executes an Cubicle script
        /// </summary>
        /// <param name="scriptFile">The path to the script file</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns></returns>
        [ExcelFunction(
            Name = "cRunScript",
            Category = "Cubicle (Misc)",
            Description = "Run an .cube script",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool RunScript(string scriptFile, string engineAlias = null)
        {
            var proxyEngine = CubicleAddIn.Engines.Get(engineAlias);
            var file = Helpers.ReadBytesFromFile(scriptFile);
            if (file == null) return false;
            ExcelAsyncUtil.QueueAsMacro(() => proxyEngine.RunScript(file));
            return true;
        }

        #endregion

        #region Functions: Async

        private const string SubscribeBufferName = "cSubscribeBuffer";

        /// <summary>
        /// Subscribe to an observable (int/double/string/DateTime) and hold a buffer
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = SubscribeBufferName,
            Category = "Cubicle (Async)",
            Description = "Subscribe to an observable and hold a buffer",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object SubscribeBuffer(string handle, int bufferSize, string engineAlias)
        {
            bufferSize = Math.Max(1, bufferSize);
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var caller = XlCall.Excel(XlCall.xlfCaller);
            var pars = new[] { handle, bufferSize, engineAlias, caller };
            var target = engine.Get(handle);
            
            if (target is IObservable<int>)
                return RxExcel.Observe(SubscribeBufferName, pars, () => ((IObservable<int>)target).InitBuffer(bufferSize, -1));

            if (target is IObservable<double>)
                return RxExcel.Observe(SubscribeBufferName, pars, () => ((IObservable<double>)target).InitBuffer(bufferSize, -1));

            if (target is IObservable<string>)
                return RxExcel.Observe(SubscribeBufferName, pars, () => ((IObservable<string>)target).InitBuffer(bufferSize, ""));

            if (target is IObservable<DateTime>)
                return RxExcel.Observe(SubscribeBufferName, pars, () => ((IObservable<DateTime>)target).InitBuffer(bufferSize, DateTime.MinValue));

            if (target is IObservable<object>)
                return RxExcel.Observe(SubscribeBufferName, pars, () => ((IObservable<object>)target).InitBuffer(bufferSize, -1));

            return "Invalid observable type";
        }

        private const string SubscribeName = "cSubscribe";

        /// <summary>
        /// Subscribe to an observable
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = SubscribeName,
            Category = "Cubicle (Async)",
            Description = "Subscribe to an observable",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Subscribe(string handle, bool shared, string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var target = engine.Get(handle);
            var caller = shared
                ? new object()
                : XlCall.Excel(XlCall.xlfCaller);
            var pars = new[] { handle, shared, engineAlias, caller };
            
            if (target is IObservable<int>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<int>)target);

            if (target is IObservable<double>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<double>)target);

            if (target is IObservable<string>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<string>)target);

            if (target is IObservable<double[]>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<double[]>)target);

            if (target is IObservable<double[,]>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<double[,]>)target);

            if (target is IObservable<double[][]>)
                return RxExcel.Observe(SubscribeName, pars, () => ((IObservable<double[][]>)target).Select(ExcelHelpers.JaggedToDouble));

            if (target is IObservable<short>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<short>)target);

            if (target is IObservable<ushort>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<ushort>)target);

            if (target is IObservable<uint>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<uint>)target);

            if (target is IObservable<long>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<long>)target);

            if (target is IObservable<ulong>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<ulong>)target);

            if (target is IObservable<char>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<char>)target);

            if (target is IObservable<DateTime>)
                return RxExcel.Observe(SubscribeName, pars, () => (IObservable<DateTime>)target);

            if (target is IObservable<object>)
                return RxExcel.Observe(SubscribeName, pars, () => ((IObservable<object>)target).Select(x => x.ToRange(false, y => y)));

            return "Invalid observable type";
        }

        #endregion

        #region Functions: Misc

        /// <summary>
        /// Returns the remote name (not the alias) of the specified Cubicle
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>The remote name (not the alias) of the specified Cubicle</returns>
        [ExcelFunction(
            Name = "cId",
            Category = "Cubicle (Misc)",
            Description = "Get the Engine ID",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string GetId(string engineAlias)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias); 
            return engine.Id;
        }

        /// <summary>
        /// Returns a list of all type aliases
        /// </summary>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Returns a list of all type aliases.</returns>
        [ExcelFunction(
            Name = "cListAliases",
            Category = "Cubicle (Misc)",
            Description = "List all type aliases",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListAliases(string engineAlias)
        {
            return TypeCache.ListTypeAliases().ToRange(true, x => new { Alias = x.Key, Type = x.Value });
        }

        /// <summary>
        /// Returns the internal log of the Add-In
        /// </summary>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="i">(Optional) The number of log items to return. Default = 100</param>
        /// <returns>The internal log of the Add-In</returns>
        [ExcelFunction(
            Name = "cLocalLog",
            Category = "Cubicle (Misc)",
            Description = "Retrieve log messages from the Excel Add-in",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] LocalLog(bool withHeadings = false, int i = 100)
        {
            return Helpers.GetMemoryLog().ToRange();
        }

        /// <summary>
        /// Returns the log of the specified Cubicle.
        /// </summary>
        /// <param name="i">(Optional) The number of log items to return. Default = 100</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns></returns>
        [ExcelFunction(
            Name = "cLog",
            Category = "Cubicle (Misc)",
            Description = "Retrieve log messages from an Cubicle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] GetLog(int i = 100, string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetLog(i);
            return data.ToRange();
        }

        #endregion

        #region Functions: Reflection

        /// <summary>
        /// Return details on all types in an assembly
        /// </summary>
        /// <param name="fullName">An assembly fullName</param>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns></returns>
        [ExcelFunction(
            Name = "cTypes",
            Category = "Cubicle (Reflection)",
            Description = "List types in an assembly",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Types(string fullName, bool withHeadings = false, string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetTypes(fullName);
            return data.ToRange(withHeadings, x => x);
        }

        /// <summary>
        /// Return type information for a specific target (either a Type name e.g. 'System.String' or Object handle e.g. '$:1000H')
        /// </summary>
        /// <param name="target">Either a Type name e.g. 'System.String' or Object handle e.g. '$:1000H'</param>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Type information of the target</returns>
        [ExcelFunction(
            Name = "cClass",
            Category = "Cubicle (Reflection)",
            Description = "Get class info form a handle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Class(string target, bool withHeadings = false, string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetType(target);
            return data.ToRange(withHeadings, x => x);
        }

        /// <summary>
        /// Lists methods of a target
        /// </summary>
        /// <param name="target">Either a Type name e.g. 'System.String' or Object handle e.g. '$:1000H'</param>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Lists methods of a target</returns>
        [ExcelFunction(
            Name = "cMethods",
            Category = "Cubicle (Reflection)",
            Description = "Get method info from a handle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Methods(string target, bool withHeadings = false, string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetMethods(target);
            return data.ToRange(withHeadings, x => x);
        }

        /// <summary>
        /// Lists constructors of a target
        /// </summary>
        /// <param name="target">Either a Type name e.g. 'System.String' or Object handle e.g. '$:1000H'</param>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Lists constructors of a target</returns>
        [ExcelFunction(
            Name = "cConstructors",
            Category = "Cubicle (Reflection)",
            Description = "Get constructor info from a type name",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Constructors(
            string target,
            bool withHeadings = false,
            string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var rawData = engine.GetConstructors(target);
            var data = rawData.SelectMany((x, i) => x.Parameters.Select(p => new
            {
                ConstructorId = i,
                ParameterId = p.Id,
                p.Name,
                p.ParameterType
            })).ToList();
            return data.ToRange(withHeadings);
        }

        /// <summary>
        /// Lists fields of a target
        /// </summary>
        /// <param name="target">Either a Type name e.g. 'System.String' or Object handle e.g. '$:1000H'</param>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Lists fields of a target</returns>
        [ExcelFunction(
            Name = "cFields",
            Category = "Cubicle (Reflection)",
            Description = "Get field info from a handle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Fields(
            string target,
            bool withHeadings = false,
            string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetFields(target);
            return data.ToRange(withHeadings);
        }

        /// <summary>
        /// Lists properties of a target
        /// </summary>
        /// <param name="target">Either a Type name e.g. 'System.String' or Object handle e.g. '$:1000H'</param>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns></returns>
        [ExcelFunction(
            Name = "cProperties",
            Category = "Cubicle (Reflection)",
            Description = "Get property info from a handle",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Properties(
            string target,
            bool withHeadings = false,
            string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetProperties(target);
            return data.ToRange(withHeadings, x => x);
        }

        /// <summary>
        /// Lists parameters of a methods on a target
        /// </summary>
        /// <param name="target">Either a Type name e.g. 'System.String' or Object handle e.g. '$:1000H'</param>
        /// <param name="methodName">The method name</param>
        /// <param name="offset">(Optional) Only use for overloaded methods. The number of the overload to use.</param>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>Lists parameters of a methods on a target</returns>
        [ExcelFunction(
            Name = "cParameters",
            Category = "Cubicle (Reflection)",
            Description = "Get parameter info from a handle and method name",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Parameters(
            string target,
            string methodName,
            int offset = 0,
            bool withHeadings = false,
            string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetParameterData(target, methodName, offset);
            return data.ToRange(withHeadings, x => x);
        }

        public static object ConvertAsm(Assembly asm)
        {
            var name = asm.GetName();
            return new
            {
                asm.FullName,
                name.ProcessorArchitecture,
                asm.ImageRuntimeVersion,
                asm.GlobalAssemblyCache,
                asm.HostContext,
                Location = asm.IsDynamic ? "Dynamic assembly" : asm.Location,
                asm.IsFullyTrusted,
                name.Name,
                name.Version,
                name.CultureInfo.DisplayName
            };
        }

        /// <summary>
        /// List the assemblies in the Add-In App-Domain
        /// </summary>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <returns>List the assemblies in the Add-In App-Domain</returns>
        [ExcelFunction(
            Name = "cLocalAssemblies",
            Category = "Cubicle (Reflection)",
            Description = "List loaded assemblies",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Assemblies(bool withHeadings)
        {
            var data = AppDomain.CurrentDomain.GetAssemblies();
            return data.ToRange(withHeadings, ConvertAsm);
        }

        /// <summary>
        /// List the assemblies loaded into an Cubicle
        /// </summary>
        /// <param name="withHeadings">(Optional) Set to true to return column names. Default is false.</param>
        /// <param name="engineAlias">(Optional) An alias for this Cubicle. Default is used on omition</param>
        /// <returns>The assemblies loaded into an Cubicle</returns>
        [ExcelFunction(
            Name = "cAssemblies",
            Category = "Cubicle (Reflection)",
            Description = "List Cubicle assemblies",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] Assemblies(bool withHeadings = false, string engineAlias = null)
        {
            var engine = CubicleAddIn.Engines.Get(engineAlias);
            var data = engine.GetAssemblyDetails();
            return data.ToRange(withHeadings, x => x);
        }

        #endregion

    }
}
